
try:
    import cgitb
    from mod_python import apache
    from mod_python.util import FieldStorage
except:
    # called outside of apache for debugging
    pass

import os, time

from dateutil import parser as dateparser

def now():
    return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())


class BaseProxyHandler:

    def __init__(self, d):
        if d[0] != '/':
            d = '/' + d
        self.urldir = d
        # Change this to your own hostname
        self.host = "http://mementoproxy.lanl.gov"

        self.error_tmpl = "<html><body><br/><center><table width='800px'><tr><td><div style='background-color: #e0e0e0; padding: 10px;'><br/><center><b>Error: %s</b></center>%s<br/><br/></div></td></tr></table></body></html>"


    def send(self, data, req, status=302, ct="text/plain"):
        req.status = status
        req.content_type = ct
        req.content_length = len(data)
        req.send_http_header()
        if type(data) == unicode:
            req.write(data.encode('utf-8'))
        else:
            req.write(data)

    def error(self, data, req, status=400, ct="text/plain"):
        data = self.error_tmpl % (status, data)
        self.send(data, req, status, ct='text/html')
        return None

    def fetch_changes(self, req, requri, dt=None):
        # This is what to implement per proxy
        # It should return a list of 2-tuples, the first element of each being a datetime, the second a URI
        raise NotImplementedError()

    def fetch_memento(self, req, requri, dt=None):
        # This is what to implement per proxy
        # It should return a list of 2-tuples, the first element of each being a datetime, the second a URI
        raise NotImplementedError()


    def handle_timemap(self, req):
        # This generates the TimeMap
        urlOffset = len(self.urldir) + len('/timemap/link/')
        requri = req.unparsed_uri[urlOffset:]

        if requri.startswith('http/'):
            requri = requri.replace('http/', 'http://')
        if not requri.startswith('http://'):
            requri = "http://" + requri
        
        changes = self.fetch_changes(req, requri)

        # If timegate redirects, we don't have a timemap of our own
        if type(changes) == dict:
            return None

        if not changes or type(changes) == str:
            return self.error('Resource not in archive<br/><blockquote>Resource: %s</blockquote>' % requri, req, status=404)
        
        fmtstr = "%a, %d %b %Y %H:%M:%S GMT"
        tguri = self.host + self.urldir + '/timegate/' + requri
        links = ['<http://%s%s>;rel="self";type="application/link-format"' % (self.host, req.unparsed_uri)]
        links.append('<%s>;rel="timegate"' % tguri)
        links.append('<%s>;rel="original"' % requri)
            
        if len(changes) == 1:
            links.append('<%s>;rel="first last memento";datetime="%s"' % (changes[0][1], changes[0][0].strftime(fmtstr)))
        else:
            links.append('<%s>;rel="first memento";datetime="%s"' % (changes[0][1], changes[0][0].strftime(fmtstr)))
            for ch in changes[1:-1]:
                links.append('<%s>;rel="memento";datetime="%s"' % (ch[1], ch[0].strftime(fmtstr)))
            links.append('<%s>;rel="last memento";datetime="%s"' % (changes[-1][1], changes[-1][0].strftime(fmtstr)))
        data = ',\n '.join(links)

        # Add link header with anchored timemap
        tglink = '<http://%s%s>;rel="timemap";type="application/link-format";anchor="%s"' % (self.host, req.unparsed_uri, requri)
        req.err_headers_out['Link'] = tglink
        return self.send(data, req, status=200, ct='application/link-format')


    def handle_dt(self, req):
        nowd = now()
        current = dateparser.parse(nowd)

        ### Process Request information
        try:
            reqdate = req.headers_in['accept-datetime']
        except KeyError:
            reqdate = nowd

        urlOffset = len(self.urldir) + len('/timegate/')
        if req.unparsed_uri.startswith(self.host):
            urlOffset += len(self.host)
        requri = req.unparsed_uri[urlOffset:]
        
        if requri.startswith('http/'):
            requri = requri.replace('http/', 'http://')
        if not requri.startswith('http'):
            requri = "http://" + requri

        try:    
            wanted = dateparser.parse(reqdate)
            if (wanted.tzinfo == None or  wanted.tzinfo.utcoffset(wanted) == None):
                # Naive date. Reparse with Timezone
                reqdate += " GMT"
                wanted = dateparser.parse(wanted)
        except:
            # Unparseable, but we need to fetch the list anyway for header :(
            wanted = None        
            
        # Database Access via overridden fetch_changes
        changes = self.fetch_changes(req, requri, dt=wanted)

        # Setup response information: link headers
        if changes and type(changes) == list:
            first = changes[0]
            last = changes[-1]
        else:
            first = None
            last = None

        next = None
        prev = None

        tmap = self.host + self.urldir + '/timemap/link/' + requri
        links = ['<%s>;rel="original"' % requri,
                 '<%s>;rel="timemap";type="application/link-format"' % tmap]

        # Process Error Conditions
        req.err_headers_out['Vary'] = 'negotiate,accept-datetime'

        if type(changes) == dict:
            #  XXX Should never occur?
            return None
        elif type(changes) == str:
            # Redirect to better TimeGate
            req.err_headers_out['Location'] = changes            
            req.err_headers_out['Link'] = '<%s>;rel="original"' % requri
            return self.send('Redirecting to better TimeGate: %s' % changes, req, status=302)

        else:
            # check VERB used for GET/HEAD
            if not changes:
                return self.error('Resource not in archive<br/><i><blockquote><ul><li>Resource: %s</li></ul></blockquote></i>' % requri, req, status=404)
            elif req.method != "GET" and req.method != "HEAD":
                # 405
                req.err_headers_out['Allow'] = "GET, HEAD"
                req.err_headers_out['Link'] = self.construct_linkhdr(links, first, last)
                return self.error("Only GET and HEAD allowed", req, status=405)
            elif wanted == None:
                fmtstr = "%a, %d %b %Y %H:%M:%S GMT"
                req.err_headers_out['Link'] = self.construct_linkhdr(links, first, last)
                return self.error("Datetime format not correct<br/><i><blockquote><ul><li>Date: %s</li><li>Expected: %s</li></ul></blockquote></i>" % (reqdate, current.strftime(fmtstr)), req, status=400)                

        
        if wanted == current or len(changes) == 1:
            # return last (or only)
            loc = last
            next = None
            prev = None
        elif wanted < first[0]:
            loc = first
            next = changes[1]
        elif wanted > last[0]:
            loc = last
            prev = changes[-1]
        else:
            tdiff = lambda y,x: float(abs((y-x).days * 86400) + abs((y-x).seconds))
            # Else find closest
            for c in range(1, len(changes)):
                this = changes[c]
                if wanted < this[0] or c == len(changes)-1:
                    llast = changes[c-1]
                    tdelta1 = tdiff(llast[0], wanted)
                    tdelta2 = tdiff(this[0], wanted)

                    if tdelta1 < tdelta2:
                        # Closest Memento to request is previous
                        loc = llast
                        if (c-2 >= 0):
                            prev = changes[c-2]
                        next = this
                    else:
                        loc = this
                        prev = llast
                        if (c < len(changes)-1):
                            next = changes[c+1]
                    break
                    
        req.err_headers_out['Link'] = self.construct_linkhdr(links, first, last, loc, next, prev)
        req.err_headers_out['Location'] = loc[1]
        return self.send('', req, status=302)

    def construct_linkhdr(self, links, first, last, curr=None, next=None, prev=None):
        fmtstr = "%a, %d %b %Y %H:%M:%S GMT"
        mylinks = []

        # Do first Memento
        dt = first[0].strftime(fmtstr)
        uri = first[1]
        rel = "first"
        if last and last[1] == uri:
            rel += " last"
            last = None
        if prev and prev[1] == uri:
            rel += " prev"
            prev = None
        elif curr and curr[1] == uri:
            curr = None
        rel += " memento"
        mylinks.append((uri, rel, dt))

        # If last != first:
        if last:
            dt = last[0].strftime(fmtstr)
            uri = last[1]
            rel = "last"
            if curr and curr[1] == uri:
                curr = None
            elif next and next[1] == uri:
                rel += " next"
                next = None
            rel += " memento"
            mylinks.append((uri, rel, dt))

        if prev:
            mylinks.append((prev[1], 'prev memento', prev[0].strftime(fmtstr)))
        if next:
            mylinks.append((next[1], 'next memento', next[0].strftime(fmtstr)))
        if curr:
            mylinks.append((curr[1], 'memento', curr[0].strftime(fmtstr)))

        # lh = ['<%s>;rel="%s";datetime="%s"' % x for x in mylinks]
        lh = ['<%s>;datetime="%s";rel="%s"' % (x[0], x[2], x[1]) for x in mylinks]

        links.extend(lh)
        return ','.join(links)
        


    def handle(self, req):
        urlOffset = len(self.urldir)
        if req.uri[urlOffset:urlOffset+len('/timemap/')] == '/timemap/':
            return self.handle_timemap(req)
        elif req.uri[urlOffset:urlOffset+len('/timegate/')] == '/timegate/':
            return self.handle_dt(req)
        else:
            return self.error("404: Unknown proxy command: %s" % req.uri[urlOffset:urlOffset+10], req, status=404)


def basehandler(req, hdlr):
    os.chdir('/home/web/mementoproxy')
    try:
        hdlr.handle(req)
    except:
        req.content_type = "text/html"
        cgitb.Hook(file=req).handle()
    return apache.OK


